package com.gyg.bro.utils;

import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author: gyg
 * @Description: Redis工具类
 * @date
 */
@Service
public class RedisUtil {

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    /**
     * 成功加锁标记
     */
    private static final String LOCK_SUCCESS = "OK";

    /**
     * key是否已经存在。nx必须不存在，xx必须存在
     */
    private static final String SET_IF_NOT_EXIST = "NX";

    /**
     * 过期时间的单位，px-毫秒，ex-秒
     */
    private static final String SET_WITH_EXPIRE_TIME = "EX";

    /**
     * 成功解锁标
     */
    private static final Long RELEASE_SUCCESS = 1L;


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //=============================分布式锁============================
    /**
     * 尝试获取分布式锁
     * @param lockKey 锁
     * @param value 请求标识
     * @param expireTime：单位-秒
     * @return 是否获取成功
     */
    public boolean tryGetDistributedLock(String lockKey, String value, long expireTime) {
        try {
            String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
            Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, String.valueOf(expireTime));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return false;
    }
    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        List<String> keys = new ArrayList(1);
        keys.add(lockKey);

        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, keys, requestId);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    //=============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time>0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            logger.error("redis expire:",e);
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回-1代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            logger.error("redis hasKey:",e);
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    public void del(String ... key){
        redisTemplate.delete(Lists.newArrayList(key));
    }

    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public String get(String key){

        return key== null? null : (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            logger.info("redis set:",e);
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, String value, long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            logger.error("redis set 设置时间:",e);
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public String hget(String key,String item){
        HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
        return hashOperations == null ? null : hashOperations.get(key, item);
    }

    /**
     * HashGetAll
     * @param key 键 不能为null
     * @return 值
     */
    public Map<String, ?> hgetAll(String key){
        Map entries = redisTemplate.opsForHash().entries(key);
        return entries;
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @param fields
     * @return 对应的多个键值
     */
    public List<Object> hmget(String key, final String... fields){
        return redisTemplate.opsForHash().multiGet(key, Lists.newArrayList(fields));
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,String> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            logger.error("redis hmset:",e);
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,String> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            logger.error("redis hmset:",e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,String value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            logger.error("redis hset:",e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,String value,long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            logger.error("redis hset:",e);
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, String... item){
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item,long by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item,long by){
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * key过期时间
     * @param key
     * @return
     */
    public Long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * key是否存在
     * @param key
     * @return
     */
    public boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 向集合中添加元素，如果集合key不存在则创建，如果key已经存在但是不是集合key报错
     *
     * @param key 集合key
     * @param members 元素
     * @return 添加成功则返回1，如果集合中已经存在此元素则返回0
     */
    public long sadd(final String key, final String... members){
        return redisTemplate.opsForSet().add(key, members);
    }

    /**
     * 向有序集合中添加元素，如果集合key不存在则创建
     *
     * @param key
     * @param member
     * @return 添加成功则返回1，如果集合中已经存在此元素更新score并返回0
     */
    public long zadd(final String key, final double score, final String member){

        return redisTemplate.opsForZSet().add(key, member, score) ? 1 : 0;
    }

    /**
     * 添加一个元素到列表的头部（左边)
     * @param key
     * @param string
     * @return
     */
    public long lpush(final String key, final String... string) {
        return redisTemplate.opsForList().leftPushAll(key, string);
    }

    /**
     * 添加一个元素到列表的尾部（右边）
     * @param key
     * @param string
     * @return
     */
    public long rpush(final String key, final String... string) {
        return redisTemplate.opsForList().rightPush(key, string);
    }

    public List<Object> lrange(final String key, final long start, final long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    public void ltrim(final String key, final long start, final long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    public String lindex(final String key, final long index) {
        return (String) redisTemplate.opsForList().index(key, index);
    }

    public void lset(final String key, final long index, final String value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    public Long lrem(final String key, final long count, final String value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    public String lpop(final String key) {
        return (String) redisTemplate.opsForList().leftPop(key);
    }

    public String rpop(final String key) {
        return (String) redisTemplate.opsForList().rightPop(key);
    }

}
